﻿using System;
using System.Linq;
using System.Net;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace SpringGraph
{
    [DataContract]
    public class Graph
    {
        public event EventHandler<EventArgs> Changed;

        [DataMember]
        internal Dictionary<string, Item> _nodes = new Dictionary<string,Item>();
        [DataMember]
        internal Dictionary<string, Dictionary<string, int>> _edges = new Dictionary<string, Dictionary<string, int>>();
        
        /// <summary>
        /// Se construye cuando se llama al getter de Edges, utilizando los diccioanarios _edges y _nodes
        /// </summary>
        private List<KeyValuePair<Item, Item>> edgeArray;
        
        [DataMember]
        internal string _distinguishedItemID;

        private Item _distinguishedItem
        {
            get
            {
                if (!string.IsNullOrEmpty(_distinguishedItemID))
                {
                    return _nodes.Values.Where(node => node.Id == _distinguishedItemID).FirstOrDefault();
                }

                return null;
            }
            set
            {
                _distinguishedItemID = value != null ? value.Id : string.Empty;
            }
        }

        public bool HasNode(string nodeId)
        {
            return _nodes.ContainsKey(nodeId);
        }

        public bool HasNodes()
        {
            return _nodes.Count > 0;
        }

        private void DispatchChanged()
        {
            if (Changed != null)
                Changed(this, new EventArgs());
        }

        /**
	     *  Removes an item from the graph.
	     *
	     *  @param item The item that you want to remove from the graph.
	     */
		public void Remove(Item item)
		{
			_nodes.Remove(item.Id);
			_edges.Remove(item.Id);
			
			foreach (var friends in _edges.Values) {
				if (friends.ContainsKey(item.Id))
                    friends.Remove(item.Id);
			}
			
			edgeArray = null;
            DispatchChanged();
		}

        /**
	     *  Remove the link between 2 items.
	     *
	     *  @param item1 an item in the graph that is linked to item2
	     *  @param item2 an item in the graph that is linked to item1
	     */
		public void Unlink(Item item1,Item item2)
		{
			var friends = _edges[item1.Id];
			friends.Remove(item2.Id);
			
			friends = _edges[item2.Id];
			friends.Remove(item1.Id);
			
			edgeArray = null;
			DispatchChanged();
		}

        public List<KeyValuePair<Item, Item>> Edges
        {
            get
            {
                if (edgeArray == null)
                {
                    edgeArray = new List<KeyValuePair<Item, Item>>();

                    Dictionary<string, bool> dones = new Dictionary<string, bool>();
                    foreach (var id in _edges.Keys)
                    {
                        dones[id] = true;

                        var friends = _edges[id];
                        foreach (var friendId in friends.Keys)
                        {
                            if (!dones.ContainsKey(friendId))
                            {
                                edgeArray.Add(new KeyValuePair<Item, Item>(_nodes[id], _nodes[friendId]));
                            }
                        }
                    }
                }

                return edgeArray;
            }
        }

        public List<Item> Nodes
        {
            get
            {
                return _nodes.Select(pair => pair.Value).ToList();
            }
        }
        

        public void Link(Item item1,Item item2)
		{
			var friends = _edges[item1.Id];
			friends[item2.Id] = 0;
			
			friends = _edges[item2.Id];
			friends[item1.Id] = 0;
			
			edgeArray = null;
            DispatchChanged();
		}

        public void Add(Item item)
		{
			if(_distinguishedItem == null)
				_distinguishedItem = item;
				
			if(_nodes.ContainsKey(item.Id)) {
				return;
			}
			
			_nodes[item.Id] = item;
            _edges[item.Id] = new Dictionary<string, int>();
            DispatchChanged();
		}

        public bool Linked(Item item1,Item item2)
		{
            var friends = _edges[item1.Id];
            return (friends != null) && friends.ContainsKey(item2.Id);
		}

        public int NumLinks(Item item) 
        {
			var friends = _edges[item.Id];
			
			return friends.Count;
		}

        public Item Find(string id) 
        {
			if(_nodes.ContainsKey(id))
				return _nodes[id];
			else
				return null;
		}

        public IEnumerable<Item> FindByDataContext(object dataContext)
        {
            return _nodes.Values.Where(node => node.DataContext == dataContext);
        }

        public List<Item> Neighbors(string id) 
        {
			return (from ed in _edges[id].Keys
                   select Find(ed) ).ToList();
		}

        public Item DistinguishedItem
        {
            get
            {
                return _distinguishedItem;
            }
            set
            {
                _distinguishedItem = value;
            }
        }

        /** Remove all items from the graph. */
		public void Empty() 
        {
            _nodes = new Dictionary<string, Item>();
            _edges = new Dictionary<string, Dictionary<string, int>>();
			edgeArray = null;
			_distinguishedItem = null;
            DispatchChanged();	
		}
    }
}
